01 - 窗口里装了什么
前置:无。本篇是专题起点。
本篇回答:一次请求的 token 具体花在哪五个地方,它们各自怎么增长,以及在动手优化之前该先量什么。
本篇会用到的词:
| 词 | 意思 |
|---|---|
| 渲染顺序 | 一次请求拼成 token 序列时的固定顺序:tools → system → messages。它决定了什么在前缀里 |
| 增长阶 | 一项开销随轮次增长的方式:常数(每轮固定)、线性(每轮加一点)、或者更快 |
| 有效利用率 | 上下文里真正对当前这一步有用的 token 占比。它是这一层唯一值得优化 的目标 |
| 计数接口 | count_tokens,在不真正发起推理的前提下算出这次请求有多少输入 token |
一、上下文工程不是提示工程
两者的分界很清楚:
二、五个去向
三、量一遍:一次真实构成
不要凭感觉猜。count_tokens 可以在不发起推理的前提下算出这次请求的输入量:
// 逐项分别数,才知道钱花在哪。只数总量等于什么都没数。
const parts = {
// 工具定义单独数:把 tools 传进去、messages 给一条空的最小消息
tools: await client.messages.countTokens({
model: "claude-opus-5", tools, messages: [{ role: "user", content: "." }],
}),
// 系统提示词同理
system: await client.messages.countTokens({
model: "claude-opus-5", system, messages: [{ role: "user", content: "." }],
}),
// 完整请求
full: await client.messages.countTokens({
model: "claude-opus-5", tools, system, messages,
}),
};
// 对话历史 + 工具结果 ≈ full − tools − system(那条 "." 的量可以忽略)
在一个典型的编码 Agent 上跑这段代码,第 1 轮和第 40 轮的构成完全是两回事:
四、有效利用率:这一层唯一值得优化的目标
省 token 本身不是目标 —— 把上下文砍到只剩一句话,token 最省,效果最差。真正的目标是提高有效利用率:
有效利用率 = 对当前这一步真正有用的 token ÷ 这一步实际发出的 token
它没法精确测,但可以估。一个可操作的近似做法:
# 对一批真实请求,把上下文按块拆开,让一个便 宜模型逐块判断
# 「这一块对回答当前这个问题有没有帮助」,统计有用块的 token 占比。
#
# 判断的粒度很重要:
# - 太粗(整段历史算一块)→ 结论永远是"有用",测不出东西
# - 太细(每条消息一块)→ 会把提供背景的铺垫消息误判成无用
# 实践中按「一次工具调用及其结果」为一块比较合适。
def effective_ratio(request, judge_model="claude-haiku-4-5"):
blocks = split_into_blocks(request) # 按工具调用/结果切
useful = [b for b in blocks if judge(b, request.current_question, judge_model)]
return sum(t(b) for b in useful) / sum(t(b) for b in blocks)
典型结果会比直觉低得多。一个跑到第 40 轮的编码 Agent,有效利用率常在 10% 到 20% —— 也就是说八成以上的输入 token 对当前这一步没有帮助,但它们依然在消耗注意力(02 篇)和费用。
这个数字的用处不在绝对值,在改造前后的对比:做了一轮裁剪之后,有效利用率有没有上去、任务成功率有没有跟着上去。只看 token 省了多少,会得出"砍得越狠越好"的错误结论。
五、预算怎么分配
一个可用的起点,按上下文窗口的百分比给:
| 去向 | 建议上限 | 超了怎么办 |
|---|---|---|
| 工具定义 | 5%(或 10K token,取小) | 上延迟加载与工具搜索(05 篇) |
| 系统提示词 | 2% | 通常不是问题;如果超了,多半是把本该做成工具描述的内容写进去了 |
| 记忆与检索注入 | 5% | 收紧召回条数,安全类记忆单独走强制注入 |
| 对话历史 | 20% | 触发压缩(03 篇) |
| 工具结果 | 剩下的全部 | 触发裁剪或卸载(03、04 篇) |
三条原则:
- 常数项要卡死上 限。工具定义和系统提示词是每轮重复付的,它们超标的代价随轮次线性放大
- 给工具结果留最大的一块,因为它是任务真正在推进的证据;但也正因为量大,它必须有清理机制
- 不要把窗口用满。留出 20% 到 30% 的余量:模型在接近上限时的表现下滑最明显,而且突发的大工具结果需要有地方放
六、小结
- 提示工程管的是开发时写一段文本,上下文工程管的是运行时每一轮的取舍;Agent 让后者成为主要矛盾
- 五个去向里,工具定义是"还没干活就付掉的常数",工具结果是"干活过程中累积的变量",两者要用不同手段治
- 动手前先用
count_tokens逐项量一遍;第 1 轮的构成和第 40 轮完全不同,只看其中一个会做错决策 - 优化目标是有效利用率而不是总 token;只看省了多少,会得出"砍得越狠越好"的错误结论
- 常数项卡死上限,工具结果留最大一块但必须有清理机制,窗口留 20% 到 30% 余量
下一篇:02 - 上下文腐烂,为什么"反正窗口够大,多塞点没坏处"这个假设不成立。
← 回到 专题索引